use pest::Parser;
use pest_derive::Parser;
use std::fs;

#[derive(Parser)]
#[grammar = "sysy.pest"]
pub struct SysYParser;

#[derive(Debug, PartialEq)]
pub enum Token {
    Const,
    Int,
    Void,
    If,
    Else,
    While,
    Break,
    Continue,
    Return,
    Plus,
    Minus,
    Mul,
    Div,
    Mod,
    Assign,
    Eq,
    Neq,
    Lt,
    Gt,
    Le,
    Ge,
    And,
    Or,
    LParen,
    RParen,
    LBrace,
    RBrace,
    LBrackt,
    RBrackt,
    Comma,
    Semicolon,
    Not,
    Ident(String),
    IntegerConst(i32),
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() != 2 {
        eprintln!("Usage: {} <file.sy>", args[0]);
        std::process::exit(1);
    }

    let filename = &args[1];
    let content = fs::read_to_string(filename).expect("Failed to read file");

    let pairs = match SysYParser::parse(Rule::program, &content) {
        Ok(pairs) => pairs,
        Err(e) => {
            // 获取错误位置
            let line = match e.line_col {
                pest::error::LineColLocation::Pos((line, _)) => line,
                pest::error::LineColLocation::Span((line, _), _) => line,
            };
            eprintln!("Error type A at Line {}: {}", line, e);
            std::process::exit(1);
        }
    };

    let mut tokens = Vec::new();
    let mut errors = Vec::new();

    for pair in pairs {
        let span = pair.as_span();
        let line_no = content[..span.start()].lines().count() + 1;

        match pair.as_rule() {
            Rule::CONST => tokens.push((line_no, Token::Const)),
            Rule::INT => tokens.push((line_no, Token::Int)),
            Rule::VOID => tokens.push((line_no, Token::Void)),
            Rule::IF => tokens.push((line_no, Token::If)),
            Rule::ELSE => tokens.push((line_no, Token::Else)),
            Rule::WHILE => tokens.push((line_no, Token::While)),
            Rule::BREAK => tokens.push((line_no, Token::Break)),
            Rule::CONTINUE => tokens.push((line_no, Token::Continue)),
            Rule::RETURN => tokens.push((line_no, Token::Return)),
            Rule::PLUS => tokens.push((line_no, Token::Plus)),
            Rule::MINUS => tokens.push((line_no, Token::Minus)),
            Rule::MUL => tokens.push((line_no, Token::Mul)),
            Rule::DIV => tokens.push((line_no, Token::Div)),
            Rule::MOD => tokens.push((line_no, Token::Mod)),
            Rule::ASSIGN => tokens.push((line_no, Token::Assign)),
            Rule::EQ => tokens.push((line_no, Token::Eq)),
            Rule::NEQ => tokens.push((line_no, Token::Neq)),
            Rule::LT => tokens.push((line_no, Token::Lt)),
            Rule::GT => tokens.push((line_no, Token::Gt)),
            Rule::LE => tokens.push((line_no, Token::Le)),
            Rule::GE => tokens.push((line_no, Token::Ge)),
            Rule::AND => tokens.push((line_no, Token::And)),
            Rule::OR => tokens.push((line_no, Token::Or)),
            Rule::L_PAREN => tokens.push((line_no, Token::LParen)),
            Rule::R_PAREN => tokens.push((line_no, Token::RParen)),
            Rule::L_BRACE => tokens.push((line_no, Token::LBrace)),
            Rule::R_BRACE => tokens.push((line_no, Token::RBrace)),
            Rule::L_BRACKT => tokens.push((line_no, Token::LBrackt)),
            Rule::R_BRACKT => tokens.push((line_no, Token::RBrackt)),
            Rule::COMMA => tokens.push((line_no, Token::Comma)),
            Rule::SEMICOLON => tokens.push((line_no, Token::Semicolon)),
            Rule::NOT => tokens.push((line_no, Token::Not)),
            Rule::IDENT => tokens.push((line_no, Token::Ident(pair.as_str().to_string()))),
            Rule::INTEGER_CONST => {
                let s = pair.as_str();
                let value = if s.starts_with("0x") || s.starts_with("0X") {
                    i32::from_str_radix(&s[2..], 16).unwrap_or_else(|_| {
                        errors.push(format!("Error type A at Line {}: Invalid hexadecimal number '{}'", line_no, s));
                        0
                    })
                } else if s.starts_with('0') && s.len() > 1 {
                    i32::from_str_radix(&s[1..], 8).unwrap_or_else(|_| {
                        errors.push(format!("Error type A at Line {}: Invalid octal number '{}'", line_no, s));
                        0
                    })
                } else {
                    s.parse().unwrap_or_else(|_| {
                        errors.push(format!("Error type A at Line {}: Invalid decimal number '{}'", line_no, s));
                        0
                    })
                };
                tokens.push((line_no, Token::IntegerConst(value)));
            }
            Rule::COMMENT | Rule::WHITESPACE => continue,
            _ => {
                errors.push(format!("Error type A at Line {}: Mysterious character '{}'", line_no, pair.as_str()));
            }
        }
    }

    if !errors.is_empty() {
        for error in errors {
            eprintln!("{}", error);
        }
        std::process::exit(1);
    } else {
        for (line_no, token) in tokens {
            eprintln!("{:?} at Line {}.", token, line_no);
        }
    }
}